--- title: "02-CanvasChain 第1周 - 基础架构搭建详细设计文档" created: 2025-11-28 aliases: - CanvasChain 第1周 - 基础架构搭建详细设计文档 tags: - 项目 --- # 📋 CanvasChain 第1周 - 基础架构搭建详细设计文档 ## 📊 **1. 项目架构总览** ![[第一周-2e2d968c.png]] --- ## 🗂️ **2. Maven 多模块项目结构设计** ```text canvas-chain/ │ ├── canvas-chain-common/ # 【模块1】通用模块 │ ├── pom.xml │ ├── src/main/java/ │ │ ├── com/canvas/chain/ │ │ │ ├── entity/ # ✅ 核心实体类 │ │ │ │ ├── User.java │ │ │ │ ├── Artwork.java │ │ │ │ ├── Auction.java │ │ │ │ ├── Transaction.java │ │ │ │ ├── Vote.java │ │ │ │ ├── Wallet.java │ │ │ │ ├── BlindBox.java │ │ │ │ └── NFT.java │ │ │ │ │ │ │ ├── dto/ # ✅ 数据传输对象 │ │ │ │ ├── request/ │ │ │ │ │ ├── LoginRequest.java │ │ │ │ │ ├── RegisterRequest.java │ │ │ │ │ └── ... │ │ │ │ └── response/ │ │ │ │ ├── LoginResponse.java │ │ │ │ ├── UserResponse.java │ │ │ │ └── ... │ │ │ │ │ │ │ ├── enums/ # ✅ 枚举类 │ │ │ │ ├── UserRole.java │ │ │ │ ├── AuctionStatus.java │ │ │ │ ├── TransactionStatus.java │ │ │ │ └── ... │ │ │ │ │ │ │ ├── util/ # ✅ 工具类 │ │ │ │ ├── IdGenerator.java # ID 生成 │ │ │ │ ├── JwtUtil.java # JWT 工具 │ │ │ │ ├── EncryptUtil.java # 加密工具 │ │ │ │ └── DateUtil.java │ │ │ │ │ │ │ ├── exception/ # ✅ 异常定义 │ │ │ │ ├── BusinessException.java │ │ │ │ ├── AuthException.java │ │ │ │ └── ... │ │ │ │ │ │ │ ├── config/ # ✅ 公共配置 │ │ │ │ ├── JacksonConfig.java │ │ │ │ └── RestTemplateConfig.java │ │ │ │ │ │ │ └── constant/ # ✅ 常量定义 │ │ │ ├── SystemConstant.java │ │ │ └── ErrorCode.java │ │ │ │ │ └── resources/ │ │ └── application-common.yml # 通用配置 │ │ │ └── pom.xml │ ├── canvas-chain-gateway/ # 【模块2】API 网关 │ ├── pom.xml │ ├── src/main/java/ │ │ └── com/canvas/chain/gateway/ │ │ ├── config/ │ │ │ └── NacosConfig.java # Nacos 动态配置 │ │ ├── filter/ │ │ │ ├── AuthFilter.java # 认证过滤器 │ │ │ └── LoggingFilter.java # 日志过滤器 │ │ ├── handler/ │ │ │ └── GlobalExceptionHandler.java │ │ └── Application.java │ │ │ ├── resources/ │ │ └── application.yml │ │ │ └── Dockerfile │ ├── canvas-chain-user-service/ # 【模块3】用户服务 │ ├── pom.xml │ ├── src/main/java/ │ │ └── com/canvas/chain/user/ │ │ ├── controller/ │ │ │ └── UserController.java # ✅ 用户接口 │ │ ├── service/ │ │ │ ├── UserService.java # 接口 │ │ │ └── impl/ │ │ │ └── UserServiceImpl.java # 实现 │ │ ├── mapper/ │ │ │ └── UserMapper.java # MyBatis mapper │ │ ├── entity/ │ │ │ └── User.java │ │ ├── config/ │ │ │ ├── MybatisPlusConfig.java │ │ │ └── DataSourceConfig.java │ │ └── Application.java │ │ │ ├── resources/ │ │ ├── application.yml │ │ └── application-dev.yml │ │ │ └── Dockerfile │ ├── canvas-chain-artwork-service/ # 【模块4】创意服务 │ ├── pom.xml │ ├── src/main/java/ │ │ └── com/canvas/chain/artwork/ │ │ ├── controller/ │ │ ├── service/ │ │ ├── mapper/ │ │ ├── entity/ │ │ └── Application.java │ │ │ ├── resources/ │ │ └── application.yml │ │ │ └── Dockerfile │ ├── canvas-chain-auction-service/ # 【模块5】拍卖服务 │ ├── pom.xml │ ├── src/main/java/ │ │ └── com/canvas/chain/auction/ │ │ ├── controller/ │ │ ├── service/ │ │ ├── mapper/ │ │ ├── entity/ │ │ └── Application.java │ │ │ ├── resources/ │ │ └── application.yml │ │ │ └── Dockerfile │ ├── docs/ # 文档 │ ├── database/ │ │ ├── schema.sql # ✅ 数据库设计 │ │ └── init.sql │ ├── api/ │ │ └── openapi.yaml │ └── architecture.md │ ├── docker-compose.yml # ✅ 容器编排 ├── .gitignore ├── README.md └── pom.xml # 父 pom ``` --- ## 🏗️ **3. 核心 POM 文件设计** ### **3.1 父 pom.xml - 版本统一管理** ```xml 4.0.0 com.canvas.chain canvas-chain 1.0.0 pom CanvasChain - 创意交易所平台 Web3 时代的创意交易平台 canvas-chain-common canvas-chain-gateway canvas-chain-user-service canvas-chain-artwork-service canvas-chain-auction-service 11 UTF-8 UTF-8 2021.0.3 2.6.3 2021.0.3.0 2.0.4 3.5.1 3.5.9 8.0.28 2.6.3 0.11.5 5.8.1 1.18.22 1.2.79 2.13.1 2.17.1 org.springframework.cloud spring-cloud-dependencies ${spring.cloud.version} pom import org.springframework.boot spring-boot-dependencies ${spring.boot.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${alibaba.cloud.version} pom import com.alibaba.nacos nacos-client ${alibaba.nacos.version} com.baomidou mybatis-plus-boot-starter ${mybatis.plus.version} mysql mysql-connector-java ${mysql.version} io.jsonwebtoken jjwt-api ${jjwt.version} io.jsonwebtoken jjwt-impl ${jjwt.version} runtime io.jsonwebtoken jjwt-jackson ${jjwt.version} runtime cn.hutool hutool-all ${hutool.version} org.projectlombok lombok ${lombok.version} true com.alibaba fastjson ${fastjson.version} org.springframework.boot spring-boot-maven-plugin ${spring.boot.version} org.projectlombok lombok org.apache.maven.plugins maven-compiler-plugin 3.8.1 ${java.version} ${java.version} ``` ### **3.2 通用模块 pom.xml - canvas-chain-common** ```xml 4.0.0 com.canvas.chain canvas-chain 1.0.0 canvas-chain-common canvas-chain-common 通用模块 - 公共代码和工具 org.springframework.boot spring-boot-starter-web org.projectlombok lombok true cn.hutool hutool-all com.alibaba fastjson io.jsonwebtoken jjwt-api io.jsonwebtoken jjwt-impl runtime io.jsonwebtoken jjwt-jackson runtime ``` ### **3.3 用户服务 pom.xml - canvas-chain-user-service** ```xml 4.0.0 com.canvas.chain canvas-chain 1.0.0 canvas-chain-user-service canvas-chain-user-service 用户服务 - 用户管理和认证 com.canvas.chain canvas-chain-common 1.0.0 org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.baomidou mybatis-plus-boot-starter mysql mysql-connector-java org.springframework.boot spring-boot-starter-data-redis org.projectlombok lombok true ``` --- ## 📊 **4. 数据库设计** ![[ER图1-31f94d2e.png]] ### **4.1 用户表 (user)** ```sql CREATE TABLE `user` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID', `username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名', `email` VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱', `password_hash` VARCHAR(255) NOT NULL COMMENT '密码哈希', `avatar_url` VARCHAR(255) COMMENT '头像URL', `bio` TEXT COMMENT '个人简介', `role` ENUM('CREATOR', 'COLLECTOR', 'ADMIN') DEFAULT 'COLLECTOR' COMMENT '角色', `status` ENUM('ACTIVE', 'INACTIVE', 'BANNED') DEFAULT 'ACTIVE' COMMENT '状态', `follower_count` BIGINT DEFAULT 0 COMMENT '粉丝数', `following_count` BIGINT DEFAULT 0 COMMENT '关注数', `historical_rating` DECIMAL(3,2) DEFAULT 5.00 COMMENT '历史评分', `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', INDEX idx_username (username), INDEX idx_email (email), INDEX idx_status (status), INDEX idx_created_time (created_time) ) COMMENT='用户表'; ``` ### **4.2 创意作品表 (artwork)** ```sql CREATE TABLE `artwork` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '作品ID', `creator_id` BIGINT NOT NULL COMMENT '创作者ID', `title` VARCHAR(200) NOT NULL COMMENT '作品标题', `description` TEXT COMMENT '作品描述', `category` VARCHAR(50) NOT NULL COMMENT '分类', `cover_image_url` VARCHAR(255) COMMENT '封面图', `content_url` VARCHAR(255) COMMENT '内容URL(指向存储服务)', `content_type` ENUM('IMAGE', 'VIDEO', 'TEXT', 'CODE', 'DESIGN') COMMENT '内容类型', `price` DECIMAL(15,2) DEFAULT 0 COMMENT '定价', `status` ENUM('DRAFT', 'PUBLISHED', 'SOLD', 'REMOVED') DEFAULT 'DRAFT' COMMENT '状态', `view_count` BIGINT DEFAULT 0 COMMENT '浏览数', `like_count` BIGINT DEFAULT 0 COMMENT '点赞数', `rating_score` DECIMAL(3,2) DEFAULT 0 COMMENT '评分', `total_sales` DECIMAL(15,2) DEFAULT 0 COMMENT '总销售额', `settled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '已结算收入', `unsettled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '未结算收入', `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', INDEX idx_creator_id (creator_id), INDEX idx_category (category), INDEX idx_status (status), INDEX idx_created_time (created_time), INDEX idx_view_count (view_count), FOREIGN KEY (creator_id) REFERENCES `user`(id) ) COMMENT='创意作品表'; ``` ### **4.3 拍卖表 (auction)** ```sql CREATE TABLE `auction` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '拍卖ID', `artwork_id` BIGINT NOT NULL COMMENT '作品ID', `start_price` DECIMAL(15,2) NOT NULL COMMENT '起价', `current_price` DECIMAL(15,2) DEFAULT 0 COMMENT '当前价格', `reserve_price` DECIMAL(15,2) COMMENT '底价', `highest_bidder_id` BIGINT COMMENT '最高出价者ID', `auction_type` ENUM('ENGLISH', 'DUTCH', 'SEALED_BID') DEFAULT 'ENGLISH' COMMENT '拍卖方式', `status` ENUM('PENDING', 'STARTED', 'ENDED', 'SETTLED') DEFAULT 'PENDING' COMMENT '拍卖状态', `start_time` TIMESTAMP COMMENT '开始时间', `end_time` TIMESTAMP COMMENT '结束时间', `bid_count` INT DEFAULT 0 COMMENT '出价次数', `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', INDEX idx_artwork_id (artwork_id), INDEX idx_status (status), INDEX idx_end_time (end_time), INDEX idx_created_time (created_time), FOREIGN KEY (artwork_id) REFERENCES `artwork`(id), FOREIGN KEY (highest_bidder_id) REFERENCES `user`(id) ) COMMENT='拍卖表'; ``` ### **4.4 出价记录表 (bid)** ```sql CREATE TABLE `bid` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '出价ID', `auction_id` BIGINT NOT NULL COMMENT '拍卖ID', `bidder_id` BIGINT NOT NULL COMMENT '出价者ID', `bid_amount` DECIMAL(15,2) NOT NULL COMMENT '出价金额', `bid_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '出价时间', `is_highest` TINYINT(1) DEFAULT 0 COMMENT '是否最高价', INDEX idx_auction_id (auction_id), INDEX idx_bidder_id (bidder_id), INDEX idx_bid_time (bid_time), FOREIGN KEY (auction_id) REFERENCES `auction`(id), FOREIGN KEY (bidder_id) REFERENCES `user`(id) ) COMMENT='出价记录表'; ``` ### **4.5 交易表 (transaction)** ```sql CREATE TABLE `transaction` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '交易ID', `buyer_id` BIGINT NOT NULL COMMENT '买家ID', `seller_id` BIGINT NOT NULL COMMENT '卖家ID', `artwork_id` BIGINT NOT NULL COMMENT '作品ID', `transaction_type` ENUM('DIRECT_PURCHASE', 'AUCTION_WIN', 'BLINDBOX', 'COMBINED') COMMENT '交易类型', `transaction_amount` DECIMAL(15,2) NOT NULL COMMENT '交易金额', `platform_commission` DECIMAL(15,2) DEFAULT 0 COMMENT '平台抽成', `seller_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '卖家收入', `status` ENUM('PENDING', 'COMPLETED', 'FAILED', 'REFUNDED') DEFAULT 'PENDING' COMMENT '交易状态', `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `completed_time` TIMESTAMP COMMENT '完成时间', `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', INDEX idx_buyer_id (buyer_id), INDEX idx_seller_id (seller_id), INDEX idx_artwork_id (artwork_id), INDEX idx_status (status), INDEX idx_created_time (created_time), FOREIGN KEY (buyer_id) REFERENCES `user`(id), FOREIGN KEY (seller_id) REFERENCES `user`(id), FOREIGN KEY (artwork_id) REFERENCES `artwork`(id) ) COMMENT='交易表'; ``` ### **4.6 钱包表 (wallet)** ```sql CREATE TABLE `wallet` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '钱包ID', `user_id` BIGINT NOT NULL UNIQUE COMMENT '用户ID', `balance` DECIMAL(15,2) DEFAULT 0 COMMENT '余额', `locked_balance` DECIMAL(15,2) DEFAULT 0 COMMENT '冻结余额', `total_recharge` DECIMAL(15,2) DEFAULT 0 COMMENT '总充值', `total_withdraw` DECIMAL(15,2) DEFAULT 0 COMMENT '总提现', `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', INDEX idx_user_id (user_id), FOREIGN KEY (user_id) REFERENCES `user`(id) ) COMMENT='用户钱包表'; ``` ### **4.7 投票表 (vote)** ```sql CREATE TABLE `vote` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '投票ID', `voter_id` BIGINT NOT NULL COMMENT '投票者ID', `artwork_id` BIGINT NOT NULL COMMENT '作品ID', `vote_weight` DECIMAL(5,2) DEFAULT 1.00 COMMENT '投票权重', `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '投票时间', UNIQUE KEY uk_voter_artwork (voter_id, artwork_id), INDEX idx_artwork_id (artwork_id), INDEX idx_voter_id (voter_id), INDEX idx_created_time (created_time), FOREIGN KEY (voter_id) REFERENCES `user`(id), FOREIGN KEY (artwork_id) REFERENCES `artwork`(id) ) COMMENT='投票表'; ``` ### **4.8 盲盒表 (blind\_box)** ```sql CREATE TABLE `blind_box` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '盲盒ID', `creator_id` BIGINT NOT NULL COMMENT '创作者ID', `title` VARCHAR(200) NOT NULL COMMENT '盲盒标题', `description` TEXT COMMENT '盲盒描述', `cover_image_url` VARCHAR(255) COMMENT '封面图', `box_price` DECIMAL(15,2) NOT NULL COMMENT '盲盒价格', `total_boxes` INT NOT NULL COMMENT '总盲盒数', `remaining_boxes` INT NOT NULL COMMENT '剩余盲盒数', `status` ENUM('CREATED', 'ACTIVE', 'SOLD_OUT', 'CLOSED') DEFAULT 'CREATED' COMMENT '状态', `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', INDEX idx_creator_id (creator_id), INDEX idx_status (status), INDEX idx_created_time (created_time), FOREIGN KEY (creator_id) REFERENCES `user`(id) ) COMMENT='盲盒表'; ``` ### **4.9 盲盒奖品表 (blind\_box\_prize)** ```sql CREATE TABLE `blind_box_prize` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '奖品ID', `blind_box_id` BIGINT NOT NULL COMMENT '盲盒ID', `artwork_id` BIGINT NOT NULL COMMENT '作品ID', `rarity` ENUM('COMMON', 'RARE', 'EPIC', 'LEGENDARY') COMMENT '稀有度', `probability_weight` INT DEFAULT 100 COMMENT '概率权重', `quantity` INT DEFAULT 1 COMMENT '数量', `remaining_quantity` INT COMMENT '剩余数量', INDEX idx_blind_box_id (blind_box_id), INDEX idx_artwork_id (artwork_id), FOREIGN KEY (blind_box_id) REFERENCES `blind_box`(id), FOREIGN KEY (artwork_id) REFERENCES `artwork`(id) ) COMMENT='盲盒奖品表'; ``` ### **4.10 盲盒开箱记录表 (blind\_box\_opening)** ```sql CREATE TABLE `blind_box_opening` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '开箱记录ID', `blind_box_id` BIGINT NOT NULL COMMENT '盲盒ID', `user_id` BIGINT NOT NULL COMMENT '用户ID', `prize_id` BIGINT NOT NULL COMMENT '获得的奖品ID', `artwork_id` BIGINT NOT NULL COMMENT '获得的作品ID', `opened_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '开箱时间', INDEX idx_blind_box_id (blind_box_id), INDEX idx_user_id (user_id), INDEX idx_opened_time (opened_time), FOREIGN KEY (blind_box_id) REFERENCES `blind_box`(id), FOREIGN KEY (user_id) REFERENCES `user`(id), FOREIGN KEY (prize_id) REFERENCES `blind_box_prize`(id), FOREIGN KEY (artwork_id) REFERENCES `artwork`(id) ) COMMENT='盲盒开箱记录表'; ``` ### **4.11 NFT 虚拟资产表 (nft)** ```sql CREATE TABLE `nft` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'NFT ID', `nft_token_id` VARCHAR(100) NOT NULL UNIQUE COMMENT 'NFT Token ID', `artwork_id` BIGINT NOT NULL COMMENT '对应作品ID', `owner_id` BIGINT NOT NULL COMMENT '当前所有者ID', `creator_id` BIGINT NOT NULL COMMENT '创作者ID', `metadata_uri` VARCHAR(255) COMMENT '元数据URI', `minting_price` DECIMAL(15,2) COMMENT '铸造价格', `mint_time` TIMESTAMP COMMENT '铸造时间', `status` ENUM('MINTED', 'LISTED', 'TRANSFERRED', 'BURNED') DEFAULT 'MINTED' COMMENT '状态', `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', INDEX idx_artwork_id (artwork_id), INDEX idx_owner_id (owner_id), INDEX idx_creator_id (creator_id), INDEX idx_status (status), FOREIGN KEY (artwork_id) REFERENCES `artwork`(id), FOREIGN KEY (owner_id) REFERENCES `user`(id), FOREIGN KEY (creator_id) REFERENCES `user`(id) ) COMMENT='NFT虚拟资产表'; ``` ### **4.12 聚合拍卖表 (combined\_auction)** ```sql CREATE TABLE `combined_auction` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '聚合拍卖ID', `title` VARCHAR(200) NOT NULL COMMENT '拍卖标题', `description` TEXT COMMENT '描述', `start_price` DECIMAL(15,2) NOT NULL COMMENT '起价', `current_price` DECIMAL(15,2) DEFAULT 0 COMMENT '当前价格', `highest_bidder_id` BIGINT COMMENT '最高出价者ID', `status` ENUM('PENDING', 'STARTED', 'ENDED', 'SETTLED') DEFAULT 'PENDING' COMMENT '状态', `start_time` TIMESTAMP COMMENT '开始时间', `end_time` TIMESTAMP COMMENT '结束时间', `artwork_count` INT COMMENT '作品数量', `bid_count` INT DEFAULT 0 COMMENT '出价次数', `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', INDEX idx_status (status), INDEX idx_end_time (end_time), INDEX idx_created_time (created_time), FOREIGN KEY (highest_bidder_id) REFERENCES `user`(id) ) COMMENT='聚合拍卖表'; ``` ### **4.13 聚合拍卖作品表 (combined\_auction\_item)** ```sql CREATE TABLE `combined_auction_item` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '项目ID', `combined_auction_id` BIGINT NOT NULL COMMENT '聚合拍卖ID', `artwork_id` BIGINT NOT NULL COMMENT '作品ID', `item_weight` INT DEFAULT 100 COMMENT '项目权重', INDEX idx_combined_auction_id (combined_auction_id), INDEX idx_artwork_id (artwork_id), FOREIGN KEY (combined_auction_id) REFERENCES `combined_auction`(id), FOREIGN KEY (artwork_id) REFERENCES `artwork`(id) ) COMMENT='聚合拍卖作品表'; ``` ### **4.14 平台账户表 (platform\_account)** ```sql CREATE TABLE `platform_account` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '账户ID', `account_name` VARCHAR(100) NOT NULL UNIQUE COMMENT '账户名', `total_commission` DECIMAL(15,2) DEFAULT 0 COMMENT '总佣金', `total_platform_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '总收入', `settled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '已结算收入', `unsettled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '未结算收入', `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' ) COMMENT='平台账户表'; ``` ### **4.15 初始化脚本 (init.sql)** ```sql -- 创建数据库 CREATE DATABASE IF NOT EXISTS canvas_chain CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE canvas_chain; -- 创建所有表... -- (上述所有表定义) -- 初始化数据 INSERT INTO `platform_account` (account_name, total_commission, created_time) VALUES ('platform_main', 0, NOW()); -- 插入平台管理员用户 INSERT INTO `user` (username, email, password_hash, role, status) VALUES ('admin', 'admin@canvas-chain.com', '$2a$10$...hashed...', 'ADMIN', 'ACTIVE'); ``` --- ## 🔐 **5. 核心实体类设计** ### **5.1 User 实体 - canvas-chain-common** ```java package com.canvas.chain.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @Builder @NoArgsConstructor @AllArgsConstructor @TableName("user") public class User implements Serializable { private static final long serialVersionUID = 1L; /** * 用户ID */ @TableId(type = IdType.AUTO) private Long id; /** * 用户名 */ private String username; /** * 邮箱 */ private String email; /** * 密码哈希 */ private String passwordHash; /** * 头像URL */ private String avatarUrl; /** * 个人简介 */ private String bio; /** * 用户角色:CREATOR(创作者)、COLLECTOR(收藏家)、ADMIN(管理员) */ private String role; /** * 账户状态:ACTIVE、INACTIVE、BANNED */ private String status; /** * 粉丝数 */ private Long followerCount; /** * 关注数 */ private Long followingCount; /** * 历史评分(1-5分) */ private BigDecimal historicalRating; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createdTime; /** * 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime; } ``` ### **5.2 Artwork 实体** ```java package com.canvas.chain.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @Builder @NoArgsConstructor @AllArgsConstructor @TableName("artwork") public class Artwork implements Serializable { private static final long serialVersionUID = 1L; /** * 作品ID */ @TableId(type = IdType.AUTO) private Long id; /** * 创作者ID */ private Long creatorId; /** * 作品标题 */ private String title; /** * 作品描述 */ private String description; /** * 分类 */ private String category; /** * 封面图URL */ private String coverImageUrl; /** * 内容URL */ private String contentUrl; /** * 内容类型:IMAGE、VIDEO、TEXT、CODE、DESIGN */ private String contentType; /** * 定价 */ private BigDecimal price; /** * 作品状态:DRAFT、PUBLISHED、SOLD、REMOVED */ private String status; /** * 浏览数 */ private Long viewCount; /** * 点赞数 */ private Long likeCount; /** * 评分 */ private BigDecimal ratingScore; /** * 总销售额 */ private BigDecimal totalSales; /** * 已结算收入 */ private BigDecimal settledRevenue; /** * 未结算收入 */ private BigDecimal unsettledRevenue; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createdTime; /** * 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime; } ``` ### **5.3 Auction 实体** ```java package com.canvas.chain.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @Builder @NoArgsConstructor @AllArgsConstructor @TableName("auction") public class Auction implements Serializable { private static final long serialVersionUID = 1L; /** * 拍卖ID */ @TableId(type = IdType.AUTO) private Long id; /** * 作品ID */ private Long artworkId; /** * 起价 */ private BigDecimal startPrice; /** * 当前价格 */ private BigDecimal currentPrice; /** * 底价 */ private BigDecimal reservePrice; /** * 最高出价者ID */ private Long highestBidderId; /** * 拍卖方式:ENGLISH、DUTCH、SEALED_BID */ private String auctionType; /** * 拍卖状态:PENDING、STARTED、ENDED、SETTLED */ private String status; /** * 开始时间 */ private LocalDateTime startTime; /** * 结束时间 */ private LocalDateTime endTime; /** * 出价次数 */ private Integer bidCount; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createdTime; /** * 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime; } ``` ### **5.4 Transaction 实体** ```java package com.canvas.chain.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @Builder @NoArgsConstructor @AllArgsConstructor @TableName("transaction") public class Transaction implements Serializable { private static final long serialVersionUID = 1L; /** * 交易ID */ @TableId(type = IdType.AUTO) private Long id; /** * 买家ID */ private Long buyerId; /** * 卖家ID */ private Long sellerId; /** * 作品ID */ private Long artworkId; /** * 交易类型:DIRECT_PURCHASE、AUCTION_WIN、BLINDBOX、COMBINED */ private String transactionType; /** * 交易金额 */ private BigDecimal transactionAmount; /** * 平台抽成 */ private BigDecimal platformCommission; /** * 卖家收入 */ private BigDecimal sellerRevenue; /** * 交易状态:PENDING、COMPLETED、FAILED、REFUNDED */ private String status; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createdTime; /** * 完成时间 */ private LocalDateTime completedTime; /** * 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime; } ``` ### **5.5 Wallet 实体** ```java package com.canvas.chain.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @Builder @NoArgsConstructor @AllArgsConstructor @TableName("wallet") public class Wallet implements Serializable { private static final long serialVersionUID = 1L; /** * 钱包ID */ @TableId(type = IdType.AUTO) private Long id; /** * 用户ID */ private Long userId; /** * 余额 */ private BigDecimal balance; /** * 冻结余额 */ private BigDecimal lockedBalance; /** * 总充值 */ private BigDecimal totalRecharge; /** * 总提现 */ private BigDecimal totalWithdraw; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createdTime; /** * 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime; } ``` --- ## 📋 **6. 枚举类定义** ### **6.1 用户角色枚举 - UserRoleEnum.java** ```java package com.canvas.chain.enums; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public enum UserRoleEnum { /** * 创作者 - 可以发布创意作品 */ CREATOR("CREATOR", "创作者"), /** * 收藏家 - 可以购买和收藏作品 */ COLLECTOR("COLLECTOR", "收藏家"), /** * 管理员 - 系统管理员 */ ADMIN("ADMIN", "管理员"); private final String code; private final String desc; public static UserRoleEnum getValue(String code) { for (UserRoleEnum userRole : UserRoleEnum.values()) { if (userRole.code.equals(code)) { return userRole; } } return null; } } ``` ### **6.2 用户状态枚举 - UserStatusEnum.java** ```java package com.canvas.chain.enums; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public enum UserStatusEnum { /** * 激活 - 正常使用 */ ACTIVE("ACTIVE", "激活"), /** * 未激活 - 等待邮箱确认 */ INACTIVE("INACTIVE", "未激活"), /** * 被禁用 - 账户被冻结 */ BANNED("BANNED", "被禁用"); private final String code; private final String desc; public static UserStatusEnum getValue(String code) { for (UserStatusEnum status : UserStatusEnum.values()) { if (status.code.equals(code)) { return status; } } return null; } } ``` ### **6.3 拍卖状态枚举 - AuctionStatusEnum.java** ```java package com.canvas.chain.enums; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public enum AuctionStatusEnum { /** * 待开始 */ PENDING("PENDING", "待开始"), /** * 进行中 */ STARTED("STARTED", "进行中"), /** * 已结束 */ ENDED("ENDED", "已结束"), /** * 已结算 */ SETTLED("SETTLED", "已结算"); private final String code; private final String desc; public static AuctionStatusEnum getValue(String code) { for (AuctionStatusEnum status : AuctionStatusEnum.values()) { if (status.code.equals(code)) { return status; } } return null; } } ``` ### **6.4 交易类型枚举 - TransactionTypeEnum.java** ```java package com.canvas.chain.enums; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public enum TransactionTypeEnum { /** * 直接购买 */ DIRECT_PURCHASE("DIRECT_PURCHASE", "直接购买"), /** * 拍卖获胜 */ AUCTION_WIN("AUCTION_WIN", "拍卖获胜"), /** * 盲盒获得 */ BLINDBOX("BLINDBOX", "盲盒获得"), /** * 组合购买 */ COMBINED("COMBINED", "组合购买"); private final String code; private final String desc; public static TransactionTypeEnum getValue(String code) { for (TransactionTypeEnum type : TransactionTypeEnum.values()) { if (type.code.equals(code)) { return type; } } return null; } } ``` ### **6.5 交易状态枚举 - TransactionStatusEnum.java** ```java package com.canvas.chain.enums; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public enum TransactionStatusEnum { /** * 待支付 */ PENDING("PENDING", "待支付"), /** * 已完成 */ COMPLETED("COMPLETED", "已完成"), /** * 已失败 */ FAILED("FAILED", "已失败"), /** * 已退款 */ REFUNDED("REFUNDED", "已退款"); private final String code; private final String desc; public static TransactionStatusEnum getValue(String code) { for (TransactionStatusEnum status : TransactionStatusEnum.values()) { if (status.code.equals(code)) { return status; } } return null; } } ``` --- ## 📍 **7. DTO 数据传输对象设计** ### **7.1 用户登录请求 DTO - LoginRequest.java** ```java package com.canvas.chain.dto.request; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class LoginRequest { /** * 用户名或邮箱 */ @NotBlank(message = "用户名或邮箱不能为空") private String username; /** * 密码 */ @NotBlank(message = "密码不能为空") private String password; } ``` ### **7.2 用户注册请求 DTO - RegisterRequest.java** ```java package com.canvas.chain.dto.request; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import javax.validation.constraints.*; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class RegisterRequest { /** * 用户名(3-20字符) */ @NotBlank(message = "用户名不能为空") @Size(min = 3, max = 20, message = "用户名长度需要3-20个字符") private String username; /** * 邮箱 */ @NotBlank(message = "邮箱不能为空") @Email(message = "邮箱格式不正确") private String email; /** * 密码(至少8位,包含大小写字母和数字) */ @NotBlank(message = "密码不能为空") @Size(min = 8, message = "密码至少需要8个字符") @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d@$!%*?&]{8,}$", message = "密码必须包含大小写字母和数字") private String password; /** * 确认密码 */ @NotBlank(message = "确认密码不能为空") private String confirmPassword; /** * 用户角色 */ @NotBlank(message = "用户角色不能为空") private String role; // CREATOR 或 COLLECTOR } ``` ### **7.3 用户登录响应 DTO - LoginResponse.java** ```java package com.canvas.chain.dto.response; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class LoginResponse { /** * JWT 令牌 */ private String token; /** * 刷新令牌 */ private String refreshToken; /** * 用户ID */ private Long userId; /** * 用户名 */ private String username; /** * 用户邮箱 */ private String email; /** * 用户角色 */ private String role; /** * 令牌有效期(秒) */ private Long expiresIn; } ``` ### **7.4 用户信息响应 DTO - UserResponse.java** ```java package com.canvas.chain.dto.response; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class UserResponse { /** * 用户ID */ private Long id; /** * 用户名 */ private String username; /** * 邮箱 */ private String email; /** * 头像URL */ private String avatarUrl; /** * 个人简介 */ private String bio; /** * 用户角色 */ private String role; /** * 账户状态 */ private String status; /** * 粉丝数 */ private Long followerCount; /** * 关注数 */ private Long followingCount; /** * 历史评分 */ private BigDecimal historicalRating; /** * 创建时间 */ private LocalDateTime createdTime; } ``` ## 📋 **7. DTO 数据传输对象设计(续)** ### **7.5 通用响应包装 DTO - ApiResponse.java** ```java package com.canvas.chain.dto.response; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ApiResponse implements Serializable { private static final long serialVersionUID = 1L; /** * 响应码 */ private String code; /** * 响应消息 */ private String message; /** * 响应数据 */ private T data; /** * 时间戳 */ private Long timestamp; /** * 成功响应 */ public static ApiResponse success(T data) { return ApiResponse.builder() .code("200") .message("成功") .data(data) .timestamp(System.currentTimeMillis()) .build(); } /** * 成功响应(带消息) */ public static ApiResponse success(String message, T data) { return ApiResponse.builder() .code("200") .message(message) .data(data) .timestamp(System.currentTimeMillis()) .build(); } /** * 失败响应 */ public static ApiResponse fail(String code, String message) { return ApiResponse.builder() .code(code) .message(message) .data(null) .timestamp(System.currentTimeMillis()) .build(); } /** * 失败响应(带数据) */ public static ApiResponse fail(String code, String message, T data) { return ApiResponse.builder() .code(code) .message(message) .data(data) .timestamp(System.currentTimeMillis()) .build(); } } ``` ### **7.6 分页响应 DTO - PageResponse.java** ```java package com.canvas.chain.dto.response; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.List; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class PageResponse implements Serializable { private static final long serialVersionUID = 1L; /** * 当前页码 */ private Long current; /** * 每页数量 */ private Long size; /** * 总记录数 */ private Long total; /** * 总页数 */ private Long pages; /** * 数据列表 */ private List records; /** * 是否有下一页 */ private Boolean hasNext; /** * 是否有上一页 */ private Boolean hasPrevious; } ``` --- ## 🛠️ **8. 工具类设计** ### **8.1 JWT 工具类 - JwtUtil.java** ```java package com.canvas.chain.util; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.HashMap; import java.util.Map; @Slf4j @Component public class JwtUtil { /** * JWT 秘钥(最少256位) */ @Value("${jwt.secret:canvas-chain-secret-key-minimum-256-bits-long}") private String secret; /** * JWT 过期时间(单位:秒,默认24小时) */ @Value("${jwt.expiration:86400}") private Long expiration; /** * 刷新令牌过期时间(单位:秒,默认7天) */ @Value("${jwt.refresh-expiration:604800}") private Long refreshExpiration; /** * 生成 JWT Token */ public String generateToken(Long userId, String username, String role) { Map claims = new HashMap<>(); claims.put("userId", userId); claims.put("username", username); claims.put("role", role); return createToken(claims, username, expiration); } /** * 生成刷新令牌 */ public String generateRefreshToken(Long userId, String username) { Map claims = new HashMap<>(); claims.put("userId", userId); claims.put("type", "refresh"); return createToken(claims, username, refreshExpiration); } /** * 创建 Token */ private String createToken(Map claims, String subject, Long expiration) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration * 1000); return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(Keys.hmacShaKeyFor( secret.getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256) .compact(); } /** * 从 Token 中获取用户ID */ public Long getUserIdFromToken(String token) { Claims claims = getAllClaimsFromToken(token); return claims.get("userId", Long.class); } /** * 从 Token 中获取用户名 */ public String getUsernameFromToken(String token) { Claims claims = getAllClaimsFromToken(token); return claims.getSubject(); } /** * 从 Token 中获取角色 */ public String getRoleFromToken(String token) { Claims claims = getAllClaimsFromToken(token); return claims.get("role", String.class); } /** * 获取所有声明 */ private Claims getAllClaimsFromToken(String token) { try { return Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor( secret.getBytes(StandardCharsets.UTF_8))) .build() .parseClaimsJws(token) .getBody(); } catch (SecurityException e) { log.error("Invalid JWT signature: {}", e.getMessage()); throw new JwtException("Invalid JWT signature", e); } catch (MalformedJwtException e) { log.error("Invalid JWT token: {}", e.getMessage()); throw new JwtException("Invalid JWT token", e); } catch (ExpiredJwtException e) { log.error("Expired JWT token: {}", e.getMessage()); throw new JwtException("Expired JWT token", e); } catch (UnsupportedJwtException e) { log.error("Unsupported JWT token: {}", e.getMessage()); throw new JwtException("Unsupported JWT token", e); } catch (IllegalArgumentException e) { log.error("JWT claims string is empty: {}", e.getMessage()); throw new JwtException("JWT claims string is empty", e); } } /** * 验证 Token 是否有效 */ public Boolean validateToken(String token) { try { Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor( secret.getBytes(StandardCharsets.UTF_8))) .build() .parseClaimsJws(token); return true; } catch (JwtException | IllegalArgumentException e) { log.error("JWT validation failed: {}", e.getMessage()); return false; } } /** * 获取 Token 过期时间 */ public Date getExpirationDateFromToken(String token) { Claims claims = getAllClaimsFromToken(token); return claims.getExpiration(); } /** * 判断 Token 是否过期 */ private Boolean isTokenExpired(String token) { try { Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } catch (ExpiredJwtException e) { return true; } } } ``` ### **8.2 密码加密工具类 - EncryptUtil.java** ```java package com.canvas.chain.util; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Component; @Component public class EncryptUtil { private static final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); /** * 密码加密 */ public static String encodePassword(String password) { return passwordEncoder.encode(password); } /** * 密码验证 */ public static Boolean matchPassword(String rawPassword, String encodedPassword) { return passwordEncoder.matches(rawPassword, encodedPassword); } } ``` ### **8.3 ID 生成工具类 - IdGenerator.java** ```java package com.canvas.chain.util; import cn.hutool.core.lang.Snowflake; import cn.hutool.core.util.IdUtil; import org.springframework.stereotype.Component; @Component public class IdGenerator { private static final Snowflake snowflake = IdUtil.getSnowflake(1, 1); /** * 生成分布式ID(雪花算法) */ public static Long nextId() { return snowflake.nextId(); } /** * 生成 UUID */ public static String nextUUID() { return IdUtil.simpleUUID(); } /** * 生成 NFT Token ID */ public static String generateNFTTokenId() { return "nft_" + System.currentTimeMillis() + "_" + IdUtil.simpleUUID(); } } ``` ### **8.4 日期工具类 - DateUtil.java** ```java package com.canvas.chain.util; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Date; public class DateUtil { private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); /** * 获取当前时间戳(毫秒) */ public static Long getCurrentTimeMillis() { return System.currentTimeMillis(); } /** * 获取当前时间戳(秒) */ public static Long getCurrentTimeSeconds() { return System.currentTimeMillis() / 1000; } /** * LocalDateTime 转换为字符串 */ public static String format(LocalDateTime dateTime) { return dateTime.format(DEFAULT_FORMATTER); } /** * LocalDateTime 转换为 Date */ public static Date toDate(LocalDateTime dateTime) { return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant()); } /** * Date 转换为 LocalDateTime */ public static LocalDateTime toLocalDateTime(Date date) { return date.toInstant() .atZone(ZoneId.systemDefault()) .toLocalDateTime(); } } ``` --- ## ❌ **9. 异常处理设计** ### **9.1 业务异常基类 - BusinessException.java** ```java package com.canvas.chain.exception; import lombok.Getter; @Getter public class BusinessException extends RuntimeException { private static final long serialVersionUID = 1L; /** * 错误码 */ private String code; /** * 错误消息 */ private String message; public BusinessException(String message) { super(message); this.code = "500"; this.message = message; } public BusinessException(String code, String message) { super(message); this.code = code; this.message = message; } public BusinessException(String code, String message, Throwable cause) { super(message, cause); this.code = code; this.message = message; } } ``` ### **9.2 认证异常 - AuthException.java** ```java package com.canvas.chain.exception; public class AuthException extends BusinessException { private static final long serialVersionUID = 1L; public AuthException(String message) { super("401", message); } public AuthException(String code, String message) { super(code, message); } } ``` ### **9.3 参数验证异常 - ValidationException.java** ```java package com.canvas.chain.exception; public class ValidationException extends BusinessException { private static final long serialVersionUID = 1L; public ValidationException(String message) { super("400", message); } } ``` ### **9.4 资源不存在异常 - ResourceNotFoundException.java** ```java package com.canvas.chain.exception; public class ResourceNotFoundException extends BusinessException { private static final long serialVersionUID = 1L; public ResourceNotFoundException(String message) { super("404", message); } } ``` ### **9.5 全局异常处理 - GlobalExceptionHandler.java** ```java package com.canvas.chain.config; import com.canvas.chain.dto.response.ApiResponse; import com.canvas.chain.exception.*; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.validation.BindingResult; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.stream.Collectors; @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 处理业务异常 */ @ExceptionHandler(BusinessException.class) @ResponseStatus(HttpStatus.OK) public ApiResponse handleBusinessException(BusinessException e) { log.error("业务异常: {}", e.getMessage(), e); return ApiResponse.fail(e.getCode(), e.getMessage()); } /** * 处理认证异常 */ @ExceptionHandler(AuthException.class) @ResponseStatus(HttpStatus.UNAUTHORIZED) public ApiResponse handleAuthException(AuthException e) { log.error("认证异常: {}", e.getMessage(), e); return ApiResponse.fail(e.getCode(), e.getMessage()); } /** * 处理资源不存在异常 */ @ExceptionHandler(ResourceNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ApiResponse handleResourceNotFoundException( ResourceNotFoundException e) { log.error("资源不存在异常: {}", e.getMessage(), e); return ApiResponse.fail(e.getCode(), e.getMessage()); } /** * 处理参数验证异常 */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiResponse handleValidationException( MethodArgumentNotValidException e) { BindingResult bindingResult = e.getBindingResult(); String errorMessage = bindingResult.getFieldErrors() .stream() .map(error -> error.getField() + ": " + error.getDefaultMessage()) .collect(Collectors.joining("; ")); log.error("参数验证异常: {}", errorMessage); return ApiResponse.fail("400", errorMessage); } /** * 处理未知异常 */ @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ApiResponse handleException(Exception e) { log.error("系统异常: {}", e.getMessage(), e); return ApiResponse.fail("500", "系统内部错误"); } } ``` --- ## ⚙️ **10. 配置类设计** ### **10.1 MyBatis Plus 配置 - MybatisPlusConfig.java** ```java package com.canvas.chain.user.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisPlusConfig { /** * 分页插件 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor( new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } } ``` ### **10.2 Web MVC 配置 - WebMvcConfig.java** ```java package com.canvas.chain.user.config; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration public class WebMvcConfig implements WebMvcConfigurer { /** * 配置消息转换器 */ @Override public void configureMessageConverters( List> converters) { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setSupportedMediaTypes(List.of( MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8 )); converters.add(0, converter); } } ``` ### **10.3 REST 模板配置 - RestTemplateConfig.java** ```java package com.canvas.chain.common.config; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; import java.time.Duration; @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder .setConnectTimeout(Duration.ofSeconds(5)) .setReadTimeout(Duration.ofSeconds(10)) .build(); } } ``` --- ## 📝 **11. application.yml 配置文件** ### **11.1 通用配置 - canvas-chain-common/resources/application-common.yml** ```yaml # 公共配置 spring: application: name: canvas-chain jackson: default-property-inclusion: non_null serialization: write-dates-as-timestamps: false indent-output: true mvc: throw-exception-if-no-handler-found: true web: resources: add-mappings: false # JWT 配置 jwt: secret: canvas-chain-secret-key-minimum-256-bits-long-for-security expiration: 86400 # 24 小时 refresh-expiration: 604800 # 7 天 # 日志配置 logging: level: root: INFO com.canvas.chain: DEBUG pattern: console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n" file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" file: name: logs/canvas-chain.log max-size: 10MB max-history: 30 ``` ### **11.2 用户服务配置 - canvas-chain-user-service/resources/application.yml** ```yaml spring: application: name: canvas-user-service profiles: active: dev cloud: nacos: discovery: server-addr: ${NACOS_SERVER_ADDR:localhost:8848} namespace: public group: DEFAULT_GROUP config: server-addr: ${NACOS_SERVER_ADDR:localhost:8848} namespace: public group: DEFAULT_GROUP file-extension: yml prefix: ${spring.application.name} datasource: url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/canvas_chain?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai username: ${MYSQL_USER:root} password: ${MYSQL_PASSWORD:root123456} driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 redis: host: ${REDIS_HOST:localhost} port: ${REDIS_PORT:6379} password: ${REDIS_PASSWORD:} timeout: 60000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0 jpa: show-sql: false hibernate: ddl-auto: validate # MyBatis Plus 配置 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.canvas.chain.entity global-config: banner: false db-config: id-type: auto table-underline: true logic-delete-value: 1 logic-not-delete-value: 0 # 服务器配置 server: port: 8001 servlet: context-path: / tomcat: threads: max: 200 min-spare: 10 # 平台信息 platform: commission-rate: 0.10 # 平台抽成 10% min-withdraw: 100 # 最低提现金额 max-withdraw: 100000 # 最高提现金额 ``` ### **11.3 网关配置 - canvas-chain-gateway/resources/application.yml** ```yaml spring: application: name: canvas-gateway cloud: gateway: routes: # 用户服务路由 - id: user-service uri: lb://canvas-user-service predicates: - Path=/api/user/** filters: - StripPrefix=1 - name: RequestRateLimiter args: key-resolver: "#{@userKeyResolver}" redis-rate-limiter.replenish-rate: 100 redis-rate-limiter.requested-tokens: 1 # 创意服务路由 - id: artwork-service uri: lb://canvas-artwork-service predicates: - Path=/api/artwork/** filters: - StripPrefix=1 - name: RequestRateLimiter args: key-resolver: "#{@userKeyResolver}" redis-rate-limiter.replenish-rate: 50 redis-rate-limiter.requested-tokens: 1 globalcors: cors-configurations: '[/**]': allow-credentials: true allowed-headers: "*" allowed-methods: - GET - POST - PUT - DELETE - OPTIONS allowed-origins: "*" max-age: 3600 nacos: discovery: server-addr: localhost:8848 namespace: public group: DEFAULT_GROUP server: port: 9000 logging: level: org.springframework.cloud.gateway: DEBUG ``` --- ## 📌 **12. 常量定义** ### **12.1 系统常量 - SystemConstant.java** ```java package com.canvas.chain.constant; public class SystemConstant { /** * 平台名称 */ public static final String PLATFORM_NAME = "CanvasChain"; /** * 平台版本 */ public static final String PLATFORM_VERSION = "1.0.0"; /** * JWT Token 前缀 */ public static final String TOKEN_HEADER = "Authorization"; /** * JWT Token 类型 */ public static final String TOKEN_TYPE = "Bearer"; /** * 默认头像 */ public static final String DEFAULT_AVATAR = "https://api.canvas-chain.com/assets/default-avatar.png"; /** * 平台佣金比率 */ public static final Double PLATFORM_COMMISSION_RATE = 0.10; // 10% /** * 最低充值金额 */ public static final Double MIN_RECHARGE_AMOUNT = 1.00; /** * 最高充值金额 */ public static final Double MAX_RECHARGE_AMOUNT = 100000.00; /** * 最低提现金额 */ public static final Double MIN_WITHDRAW_AMOUNT = 100.00; /** * 最高提现金额 */ public static final Double MAX_WITHDRAW_AMOUNT = 100000.00; } ``` ### **12.2 错误码定义 - ErrorCode.java** ```java package com.canvas.chain.constant; public class ErrorCode { // 成功 public static final String SUCCESS = "200"; public static final String SUCCESS_MESSAGE = "操作成功"; // 客户端错误 (4xx) public static final String BAD_REQUEST = "400"; public static final String BAD_REQUEST_MESSAGE = "请求参数错误"; public static final String UNAUTHORIZED = "401"; public static final String UNAUTHORIZED_MESSAGE = "未授权,请先登录"; public static final String FORBIDDEN = "403"; public static final String FORBIDDEN_MESSAGE = "禁止访问"; public static final String NOT_FOUND = "404"; public static final String NOT_FOUND_MESSAGE = "请求的资源不存在"; // 业务错误 public static final String USER_NOT_FOUND = "4001"; public static final String USER_NOT_FOUND_MESSAGE = "用户不存在"; public static final String USERNAME_ALREADY_EXISTS = "4002"; public static final String USERNAME_ALREADY_EXISTS_MESSAGE = "用户名已存在"; public static final String EMAIL_ALREADY_EXISTS = "4003"; public static final String EMAIL_ALREADY_EXISTS_MESSAGE = "邮箱已被注册"; public static final String PASSWORD_ERROR = "4004"; public static final String PASSWORD_ERROR_MESSAGE = "密码错误"; public static final String INVALID_TOKEN = "4005"; public static final String INVALID_TOKEN_MESSAGE = "无效的Token"; public static final String TOKEN_EXPIRED = "4006"; public static final String TOKEN_EXPIRED_MESSAGE = "Token已过期"; public static final String INSUFFICIENT_BALANCE = "4007"; public static final String INSUFFICIENT_BALANCE_MESSAGE = "余额不足"; // 服务器错误 (5xx) public static final String INTERNAL_SERVER_ERROR = "500"; public static final String INTERNAL_SERVER_ERROR_MESSAGE = "服务器内部错误"; public static final String SERVICE_UNAVAILABLE = "503"; public static final String SERVICE_UNAVAILABLE_MESSAGE = "服务暂时不可用"; } ``` --- **VibeCoding 导航**:⬅️ [[01-项目文档(Project Overview)|01-项目文档(Project Overview)]] | 02-CanvasChain 第1周 - 基础架构搭建详细设计文档 | ➡️ [[01-计划|01-计划]]